#ifndef ARRAY_H
#define ARRAY_H

#include <memory>
#include <vector>

template <typename T, unsigned int DIM>
class array
{
public:
	typedef T		num_type;
	typedef array<T, DIM>	array_type;
	static constexpr unsigned int n_dim = DIM;
	typedef std::vector<T>	storage_type;
	
	typedef array_iterator<T, DIM>		iterator;
	typedef array_iterator<const T, DIM>	const_iterator;
	
	array( array_storage<DIM> storage = array_storage<DIM>::c_array() ): storage_(storage) {}
	~array() {}
	
	array( const array<T, DIM>& arr );		//!< copy constructor
	array( array<T, DIM>&& arr );			//!< move constructor
	array& operator=( array<T, DIM>&& arr );//!< move assignment 
	
	array( std::initializer_list<int> shape );
	array( tiny_vector<int, DIM> shape,      array_storage<DIM> storage = array_storage<DIM>::c_array() );
	array( tiny_vector<range, DIM> range,    array_storage<DIM> storage = array_storage<DIM>::c_array() );
	
	//! for expression 
	template <typename expr_type>
	array( expression<expr_type> expr );
	
	//! for slicing
	template <typename... range_types>
	array( array& ref_arr, std::tuple<range_types...> new_slice );
	
	//! for exisiting memory block
	array( std::shared_ptr<storage_type> data, tiny_vector<int, DIM> shape,
		   array_storage<DIM> storage = array_storage<DIM>::c_array() );
		   
	array( std::shared_ptr<storage_type> data, tiny_vector<int, DIM> shape, 
		   tiny_vector<int, DIM> stride, int p_zero_offset,
		   array_storage<DIM> storage = array_storage<DIM>::c_array() );
		   
	template <typename... ID_types>
	typename array_helper::array_slice<array_type, ID_types...>::return_type 
	operator()( ID_types... id );
	
	template <typename... ID_types>
	typename array_helper::array_slice<const array_type, ID_types...>::return_type 
	operator()( ID_types... id ) const;
	
	//! multi-component
	array<typename multi_component_traits<T>::component_type, DIM>
	operator []( unsigned int component_id );
	
	template 
	<
		template<class multi_comp_T> class multi_component_checker = multi_component_traits
	>
	array<typename multi_component_checker<T>::component_type, DIM>
	extract_component( unsigned int component_id );
	
	tiny_vector<int, DIM> lbound() const { return storage_.get_base(); }
	int lbound( unsigned int dim ) const { return storage_.get_base()[dim]; }
	
	tiny_vector<int, DIM> ubound() const { return storage_.get_base() + shape_ - 1; }
	int ubound( unsigned int dim ) const { return storage_.get_base()[dim] + shape_[dim] - 1; }
	
	int num_elements() const { return vec_product( shape_ ); }
	
	void reference( array<T, DIM>& ref_arr );
	
	void slice( unsigned int dim, range r );

	template <typename... ID_types>
	void resize( ID_types&&... args );
	
	const T* data_first() const;
	T* 	 	 data_first();

	std::shared_ptr<storage_type>  data() const	{ return data_p_; }
	std::shared_ptr<storage_type>& data() 		{ return data_p_; }
	
	int  zero_offset() const { return p_zero_offset_; }
	int& zero_offset()	 	 { return p_zero_offset_; }

	const array_storage<n_dim>& storage() const { return storage_; }
	array_storage<n_dim>& 		storage() 	{ return storage_; }

	const tiny_vector<int, DIM>& shape() const 	{ return shape_; }
	tiny_vector<int, DIM>& 		 shape() 	{ return shape_; }

	const tiny_vector<int, DIM>& stride() const 	{ return stride_; }
	tiny_vector<int, DIM>& 		 stride() 	{ return stride_; }

	iterator		begin() 		{ return iterator(*this); }
	const_iterator 	begin() const 	{ return const_iterator(*this); }
	iterator		end() 			{ return iterator(); }
	const_iterator 	end() const 	{ return const_iterator(); }

	array& operator= ( std::initializer_list<T> vals);
	
	array& operator= ( const T const_val );
	array& operator+=( const T const_val );
	array& operator-=( const T const_val );
	array& operator*=( const T const_val );
	array& operator/=( const T const_val );
	array& operator%=( const T const_val );
	array& operator^=( const T const_val );
	array& operator&=( const T const_val );
	array& operator|=( const T const_val );
	array& operator>>=( const int );
	array& operator<<=( const int );
    
	array& operator= ( const array& arr );
	array& operator+=( const array& arr );
	array& operator-=( const array& arr );
	array& operator*=( const array& arr );
	array& operator/=( const array& arr );
	array& operator%=( const array& arr );
	array& operator^=( const array& arr );
	array& operator&=( const array& arr );
	array& operator|=( const array& arr );
	array& operator>>=( const array& arr );
	array& operator<<=( const array& arr );
	
	bool operator==( const array& arr );
	bool operator!=( const array& arr );
	//bool operator >( const array& arr );
	//bool operator <( const array& arr );
	//bool operator>=( const array& arr );
	//bool operator<=( const array& arr );
	
	template <class expr_T> array& operator= ( expression<expr_T>&& );
	template <class expr_T> array& operator+=( expression<expr_T>&& );
	template <class expr_T> array& operator-=( expression<expr_T>&& );
	template <class expr_T> array& operator*=( expression<expr_T>&& );
	template <class expr_T> array& operator/=( expression<expr_T>&& );
	template <class expr_T> array& operator%=( expression<expr_T>&& );
	template <class expr_T> array& operator^=( expression<expr_T>&& );
	template <class expr_T> array& operator&=( expression<expr_T>&& );
	template <class expr_T> array& operator|=( expression<expr_T>&& );
	template <class expr_T> array& operator>>=( expression<expr_T>&& );
	template <class expr_T> array& operator<<=( expression<expr_T>&& );
	
private:
	void create_new_array();
	
private:
	std::shared_ptr<storage_type> data_p_;
	array_storage<n_dim> storage_;
	tiny_vector<int, DIM> shape_;
	tiny_vector<int, DIM> stride_;
	int p_zero_offset_;
	
private:
	template <typename array_type, class... ID_types>
	friend struct array_helper::array_slice;
		
	template <typename array_type, class... ID_types>
	friend struct array_helper::array_resize;

	template <int source_dim, int n_dim, class tuple_type, class return_type, class array_type>
	friend struct array_helper::unpack_and_slice;
	
	template <class operation>
	friend struct array_helper::array_evaluator;
};



#endif //!< ARRAY_H